#include <atomic>
#include <chrono>
#include <map>
#include <thread>
#include <sstream>

#include <include/core/SkBitmap.h>
#include <include/core/SkCanvas.h>
#include <include/core/SkFont.h>
#include <include/core/SkPath.h>
#include <include/core/SkTextBlob.h>
#include <include/core/SkTypeface.h>

#include <SDL2/SDL.h>

using namespace std::chrono_literals;

class SkiaElement {
    int x_ = 0;
    int y_ = 0;
    int id_;

public:
    SkiaElement()
    {
        static int nextId = 0;
        id_ = ++nextId;
    }
    virtual ~SkiaElement() = default;

    int GetId()
    {
        return id_;
    }

    void SetPosition(int x, int y)
    {
        x_ = x;
        y_ = y;
    }

    int GetX() { return x_; }
    int GetY() { return y_; }

    virtual void Draw(SkCanvas &canvas)
    {
        canvas.translate(x_, y_);
    }
};

class LineElement : public SkiaElement {
    SkPaint fgPaint_;
    SkPath path_;

public:
    LineElement(int x1, int y1, int x2, int y2)
    {
        fgPaint_.setAntiAlias(true);
        fgPaint_.setColor(SK_ColorBLACK);
        fgPaint_.setStyle(SkPaint::kStroke_Style);
        fgPaint_.setStrokeWidth(1);

        path_.moveTo(x1, y1);
        path_.lineTo(x2, y2);
    }

    void Draw(SkCanvas &canvas) override
    {
        SkiaElement::Draw(canvas);
        canvas.drawPath(path_, fgPaint_);
    }
};

class RectElement : public SkiaElement {
    SkPaint fgPaint_;
    SkPath path_;

public:
    RectElement(int x1, int y1, int x2, int y2)
    {
        fgPaint_.setAntiAlias(true);
        fgPaint_.setColor(SK_ColorBLACK);
        fgPaint_.setStyle(SkPaint::kStroke_Style);
        fgPaint_.setStrokeWidth(1);

        path_.moveTo(x1, y1);
        path_.lineTo(x1, y2);
        path_.lineTo(x2, y2);
        path_.lineTo(x2, y1);
        path_.close();
    }

    void Draw(SkCanvas &canvas) override
    {
        SkiaElement::Draw(canvas);
        canvas.drawPath(path_, fgPaint_);
    }
};

class IElementManagerDelegate {
public:
    virtual ~IElementManagerDelegate() = default;
    virtual void SetTempElement(std::unique_ptr<SkiaElement> tele) = 0;
    virtual void AddElement(std::unique_ptr<SkiaElement> ele) = 0;
};

class SkiaController : public SkiaElement {
protected:
    std::shared_ptr<IElementManagerDelegate> elementManagerDelegate = nullptr;

public:
    virtual void SetElementManagerDelegate(const std::shared_ptr<IElementManagerDelegate> &delegate)
    {
        elementManagerDelegate = delegate;
    }

    virtual bool InRegion(int x, int y) { return false; }

    virtual void OnKeyDown(const SDL_Event &event) {}
    virtual void OnKeyUp(const SDL_Event &event) {}
    virtual void OnMouseButtonDown(const SDL_Event &event) {}
    virtual void OnMouseButtonUp(const SDL_Event &event) {}
    virtual void OnMouseMotion(const SDL_Event &event) {}
    virtual void OnMouseWheel(const SDL_Event &event) {}
};

class IconController : public SkiaController {
    SkPaint fgPaint_;
    SkPaint selectedBgPaint_;
    SkPaint hoveredBgPaint_;
    SkRect rect_ = SkRect::MakeXYWH(0, 0, 20, 15);

protected:
    bool hovered_ = false;
    bool selected_ = false;

public:
    virtual void DrawIcon(SkCanvas &canvas, SkPaint &paint) = 0;

    IconController()
    {
        fgPaint_.setAntiAlias(true);
        fgPaint_.setStyle(SkPaint::kStroke_Style);
        fgPaint_.setStrokeWidth(1);
        fgPaint_.setColor(SkColorSetARGB(0xff, 26, 106, 171));

        selectedBgPaint_.setAntiAlias(true);
        selectedBgPaint_.setColor(SkColorSetARGB(0xff, 205, 228, 252));

        hoveredBgPaint_ = selectedBgPaint_;
        hoveredBgPaint_.setColor(SkColorSetARGB(0xff, 239, 246, 254));
    }

    void SetSelected(bool selected)
    {
        selected_ = selected;
    }

    void Draw(SkCanvas &canvas) override
    {
        SkiaController::Draw(canvas);
        if (selected_) {
            canvas.drawRect(rect_, selectedBgPaint_);
            canvas.drawRect(rect_, fgPaint_);
        } else if (hovered_) {
            canvas.drawRect(rect_, hoveredBgPaint_);
            canvas.drawRect(rect_, fgPaint_);
        }

        DrawIcon(canvas, fgPaint_);
    }

    bool InRegion(int x, int y) override
    {
        return rect_.contains(x - GetX(), y - GetY());
    }

    void OnMouseMotion(const SDL_Event &event) override
    {
        auto &x = event.motion.x;
        auto &y = event.motion.y;
        hovered_ = InRegion(x, y);
    }

    void OnMouseButtonDown(const SDL_Event &event) override
    {
        if (InRegion(event.button.x, event.button.y)) {
            selected_ = !selected_;
        }
    }
};

class LineCtrl : public IconController {
    SkPath path_;

    bool creating_ = false;
    int startingX;
    int startingY;

public:
    LineCtrl()
    {
        path_.moveTo(3, 3);
        path_.lineTo(17, 12);
    }

    void DrawIcon(SkCanvas &canvas, SkPaint &paint) override
    {
        canvas.drawPath(path_, paint);
    }

    void OnMouseMotion(const SDL_Event &event) override
    {
        IconController::OnMouseMotion(event);
        if (creating_) {
            auto &x = event.motion.x;
            auto &y = event.motion.y;
            std::unique_ptr<SkiaElement> line = std::make_unique<LineElement>(startingX, startingY, x, y);
            elementManagerDelegate->SetTempElement(std::move(line));
        }
    }

    void OnMouseButtonDown(const SDL_Event &event) override
    {
        IconController::OnMouseButtonDown(event);
        if (selected_) {
            creating_ = true;
            startingX = event.button.x;
            startingY = event.button.y;
        }
    }

    void OnMouseButtonUp(const SDL_Event &event) override
    {
        IconController::OnMouseButtonUp(event);
        if (creating_) {
            creating_ = false;
            auto &x = event.button.x;
            auto &y = event.button.y;
            std::unique_ptr<SkiaElement> line = std::make_unique<LineElement>(startingX, startingY, x, y);
            elementManagerDelegate->SetTempElement(nullptr);
            elementManagerDelegate->AddElement(std::move(line));
        }
    }
};

class RectCtrl : public IconController {
    SkPath path_;

    bool creating_ = false;
    int startingX;
    int startingY;

public:
    RectCtrl()
    {
        path_.moveTo(3, 3);
        path_.lineTo(3, 11);
        path_.lineTo(16, 11);
        path_.lineTo(16, 3);
        path_.close();
    }

    void DrawIcon(SkCanvas &canvas, SkPaint &paint) override
    {
        auto aa = paint.isAntiAlias();
        paint.setAntiAlias(false);
        canvas.drawPath(path_, paint);
        paint.setAntiAlias(aa);
    }

    void OnMouseMotion(const SDL_Event &event) override
    {
        IconController::OnMouseMotion(event);
        if (creating_) {
            auto &x = event.motion.x;
            auto &y = event.motion.y;
            std::unique_ptr<SkiaElement> rect = std::make_unique<RectElement>(startingX, startingY, x, y);
            elementManagerDelegate->SetTempElement(std::move(rect));
        }
    }

    void OnMouseButtonDown(const SDL_Event &event) override
    {
        IconController::OnMouseButtonDown(event);
        if (selected_) {
            creating_ = true;
            startingX = event.button.x;
            startingY = event.button.y;
        }
    }

    void OnMouseButtonUp(const SDL_Event &event) override
    {
        IconController::OnMouseButtonUp(event);
        if (creating_) {
            creating_ = false;
            auto &x = event.button.x;
            auto &y = event.button.y;
            std::unique_ptr<SkiaElement> rect = std::make_unique<RectElement>(startingX, startingY, x, y);
            elementManagerDelegate->SetTempElement(nullptr);
            elementManagerDelegate->AddElement(std::move(rect));
        }
    }
};

class IconsController : public SkiaController {
    SkPaint fgPaint_;
    SkRect rect_ = SkRect::MakeXYWH(0, 0, 20 * 7, 15 * 3);
    std::vector<std::shared_ptr<IconController>> icons_;

    std::shared_ptr<IconController> selectedIcon_;

public:
    IconsController()
    {
        fgPaint_.setAntiAlias(true);
        fgPaint_.setStyle(SkPaint::kStroke_Style);
        fgPaint_.setStrokeWidth(1);
        fgPaint_.setColor(SkColorSetARGB(0xff, 219, 220, 221));

        auto lineCtrl = std::make_unique<LineCtrl>();
        lineCtrl->SetPosition(0, 0);
        icons_.push_back(std::move(lineCtrl));

        auto rectCtrl = std::make_unique<RectCtrl>();
        rectCtrl->SetPosition(20, 0);
        icons_.push_back(std::move(rectCtrl));
    }

    void SetElementManagerDelegate(const std::shared_ptr<IElementManagerDelegate> &delegate) override
    {
        SkiaController::SetElementManagerDelegate(delegate);
        for (auto &icon : icons_) {
            icon->SetElementManagerDelegate(delegate);
        }
    }

    void Draw(SkCanvas &canvas) override
    {
        SkiaController::Draw(canvas);
        canvas.drawRect(rect_, fgPaint_);

        for (auto &icon : icons_) {
            canvas.save();
            icon->Draw(canvas);
            canvas.restore();
        }
    }

    bool InRegion(int x, int y) override
    {
        return rect_.contains(x - GetX(), y - GetY());
    }

    void OnMouseMotion(const SDL_Event &event) override
    {
        SkiaController::OnMouseMotion(event);
        SDL_Event e = event;
        if (InRegion(event.motion.x, event.motion.y)) {
            e.motion.x -= GetX();
            e.motion.y -= GetY();
        }
        for (auto &icon : icons_) {
            icon->OnMouseMotion(e);
        }
    }

    void OnMouseButtonDown(const SDL_Event &event) override
    {
        SkiaController::OnMouseButtonDown(event);
        SDL_Event e = event;
        if (InRegion(event.button.x, event.button.y)) {
            e.button.x -= GetX();
            e.button.y -= GetY();
            for (auto &icon : icons_) {
                auto in = icon->InRegion(e.button.x, e.button.y);
                if (in) {
                    selectedIcon_ = icon;
                } else {
                    icon->SetSelected(false);
                }
            }
        }

        if (selectedIcon_ != nullptr) {
            selectedIcon_->OnMouseButtonDown(e);
        }
    }

    void OnMouseButtonUp(const SDL_Event &event) override
    {
        SkiaController::OnMouseButtonUp(event);
        SDL_Event e = event;
        if (InRegion(event.button.x, event.button.y)) {
            e.button.x -= GetX();
            e.button.y -= GetY();
        }
        if (selectedIcon_ != nullptr) {
            selectedIcon_->OnMouseButtonUp(e);
        }
    }
};

class Main : public IElementManagerDelegate, public std::enable_shared_from_this<Main> {
    SDL_Window *window_ = nullptr;
    SDL_Renderer *renderer_ = nullptr;
    struct Area {
        SDL_Surface *surface_;
        SDL_Texture *texture_;
        SDL_Rect rect_;
        void (Main::*draw)(SkBitmap &bitmap);
    };
    std::map<std::string, struct Area> areas_;

    std::unique_ptr<std::thread> uiThread_ = nullptr;
    std::atomic<bool> uiThreadRunning_ = true;

    std::vector<std::unique_ptr<SkiaController>> ctrls_;
    std::vector<std::unique_ptr<SkiaElement>> elements_;
    int tempElementId_ = 0;

public:
    Main(int winWidth, int winHeight)
    {
        SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
        constexpr auto centered = SDL_WINDOWPOS_CENTERED;
        constexpr auto flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN;
        window_ = SDL_CreateWindow("Hello Skia", centered, centered, winWidth, winHeight, flags);
        SDL_SetWindowTitle(window_, "Paint");
        SDL_SetWindowOpacity(window_, 1);

        renderer_ = SDL_CreateRenderer(window_, -1, SDL_RENDERER_ACCELERATED);
        SDL_RenderClear(renderer_);

        areas_["ctrl"] = {
            .rect_ = {0, 0, winWidth, winHeight / 8},
            .draw = &Main::DrawController,
        };
        areas_["canvas"] = {
            .rect_ = {0, winHeight / 8, winWidth, winHeight - winHeight / 8},
            .draw = &Main::DrawCanvas,
        };
    }

    ~Main()
    {
        for (auto &[_, area] : areas_) {
            if (area.surface_) {
                SDL_FreeSurface(area.surface_);
            }

            if (area.texture_) {
                SDL_DestroyTexture(area.texture_);
            }
        }

        if (renderer_) {
            SDL_DestroyRenderer(renderer_);
        }

        if (window_) {
            SDL_DestroyWindow(window_);
        }

        SDL_Quit();
    }

    void Init()
    {
        uiThread_ = std::make_unique<std::thread>(std::bind(&Main::UIThreadMain, this));

        auto iconsCtrl = std::make_unique<IconsController>();
        iconsCtrl->SetPosition(10, 10);
        iconsCtrl->SetElementManagerDelegate(shared_from_this());
        ctrls_.push_back(std::move(iconsCtrl));
    }

    void Run()
    {
        std::map<int, void(Main::*)(const SDL_Event &)> callings = {
            {SDL_QUIT, &Main::Quit},
            {SDL_KEYDOWN, &Main::OnKeyDown},
            {SDL_KEYUP, &Main::OnKeyUp},
            {SDL_MOUSEBUTTONDOWN, &Main::OnMouseButtonDown},
            {SDL_MOUSEBUTTONUP, &Main::OnMouseButtonUp},
            {SDL_MOUSEMOTION, &Main::OnMouseMotion},
            {SDL_MOUSEWHEEL, &Main::OnMouseWheel},
        };

        while (uiThreadRunning_) {
            SDL_Event e;
            while (SDL_PollEvent(&e)) {
                if (callings.find(e.type) != callings.end()) {
                    auto call = callings[e.type];
                    (this->*call)(e);
                }
            }
        }
    }

    bool SDLRectContains(SDL_Rect &rect_, int x, int y)
    {
        SkRect rect = {
            .fLeft = static_cast<float>(rect_.x),
            .fTop = static_cast<float>(rect_.y),
            .fRight = static_cast<float>(rect_.x + rect_.w),
            .fBottom = static_cast<float>(rect_.y + rect_.h),
        };
        return rect.contains(x, y);
    }

    void Quit(const SDL_Event &event)
    {
        uiThreadRunning_ = false;
        uiThread_->join();
        uiThread_ = nullptr;
    }

    void OnKeyDown(const SDL_Event &event)
    {
        for (auto &ctrl : ctrls_) {
            ctrl->OnKeyDown(event);
        }
    }

    void OnKeyUp(const SDL_Event &event)
    {
        for (auto &ctrl : ctrls_) {
            ctrl->OnKeyUp(event);
        }
    }

    void OnMouseButtonDown(const SDL_Event &event)
    {
        for (auto &ctrl : ctrls_) {
            auto &x = event.button.x;
            auto &y = event.button.y;
            if (SDLRectContains(areas_["ctrl"].rect_, x, y) && ctrl->InRegion(x, y) == false) {
                continue;
            }

            ctrl->OnMouseButtonDown(event);
        }
    }

    void OnMouseButtonUp(const SDL_Event &event)
    {
        for (auto &ctrl : ctrls_) {
            auto &x = event.button.x;
            auto &y = event.button.y;
            if (SDLRectContains(areas_["ctrl"].rect_, x, y) && ctrl->InRegion(x, y) == false) {
                continue;
            }

            ctrl->OnMouseButtonUp(event);
        }
    }

    void OnMouseMotion(const SDL_Event &event)
    {
        for (auto &ctrl : ctrls_) {
            ctrl->OnMouseMotion(event);
        }
    }

    void OnMouseWheel(const SDL_Event &event)
    {
        for (auto &ctrl : ctrls_) {
            auto &x = event.wheel.x;
            auto &y = event.wheel.y;
            if (SDLRectContains(areas_["ctrl"].rect_, x, y) && ctrl->InRegion(x, y) == false) {
                continue;
            }

            ctrl->OnMouseWheel(event);
        }
    }

    void SetTempElement(std::unique_ptr<SkiaElement> tele) override
    {
        if (tempElementId_) {
            for (auto it = elements_.begin(); it != elements_.end(); it++) {
                if ((*it)->GetId() == tempElementId_) {
                    elements_.erase(it);
                    break;
                }
            }
            tempElementId_ = 0;
        }

        if (tele != nullptr) {
            tempElementId_ = tele->GetId();
            AddElement(std::move(tele));
        }
    }

    void AddElement(std::unique_ptr<SkiaElement> ele) override
    {
        elements_.push_back(std::move(ele));
    }

    SDL_Surface *CreateSurfaceFromSkBitmap(SkBitmap &bitmap)
    {
        SkRect rect;
        bitmap.getBounds(&rect);
        constexpr uint32_t amask = 0xff000000;
        constexpr uint32_t rmask = 0x00ff0000;
        constexpr uint32_t gmask = 0x0000ff00;
        constexpr uint32_t bmask = 0x000000ff;
        return SDL_CreateRGBSurfaceFrom(bitmap.getPixels(),
            rect.width(), rect.height(), 32, rect.width() * 4, rmask, gmask, bmask, amask);
    }

    void UIThreadMain()
    {
        while (uiThreadRunning_) {
            for (auto &[name, area] : areas_) {
                SkBitmap bitmap;
                bitmap.setInfo(SkImageInfo::Make(area.rect_.w, area.rect_.h, kBGRA_8888_SkColorType, kOpaque_SkAlphaType));
                bitmap.allocPixels();

                (this->*area.draw)(bitmap);

                if (area.surface_) {
                    SDL_FreeSurface(area.surface_);
                }
                area.surface_ = CreateSurfaceFromSkBitmap(bitmap);

                if (area.texture_) {
                    SDL_DestroyTexture(area.texture_);
                }
                area.texture_ = SDL_CreateTextureFromSurface(renderer_, area.surface_);

                SDL_RenderCopy(renderer_, area.texture_, NULL, &area.rect_);
            }

            SDL_RenderPresent(renderer_);
            std::this_thread::sleep_for(16ms);
        }
    }

    void DrawController(SkBitmap &bitmap)
    {
        SkCanvas canvas(bitmap);
        canvas.clear(SkColorSetARGB(0xff, 245, 246, 247));
        canvas.translate(-areas_["ctrl"].rect_.x, -areas_["ctrl"].rect_.y);

        for (auto &ctrl : ctrls_) {
            canvas.save();
            ctrl->Draw(canvas);
            canvas.restore();
        }
    }

    void DrawCanvas(SkBitmap &bitmap)
    {
        SkCanvas canvas(bitmap);
        canvas.clear(SK_ColorWHITE);
        canvas.translate(-areas_["canvas"].rect_.x, -areas_["canvas"].rect_.y);

        for (auto &element : elements_) {
            canvas.save();
            element->Draw(canvas);
            canvas.restore();
        }
    }
};

int main()
{
    std::shared_ptr<Main> main = std::make_shared<Main>(800, 640);
    main->Init();
    main->Run();
    return 0;
}
